package utils

import java.io.{File, FileInputStream, FileOutputStream}
import java.lang.reflect.Field
import java.nio.file.Files
import java.text.SimpleDateFormat

import dao.ModeDao
import javax.imageio.ImageIO
import org.apache.batik.transcoder.image.PNGTranscoder
import org.apache.batik.transcoder.{TranscoderInput, TranscoderOutput}
import org.apache.commons.io.{FileUtils, IOUtils}
import org.apache.commons.lang3.StringUtils
import org.apache.commons.math3.stat.descriptive.moment.StandardDeviation
import org.apache.pdfbox.pdmodel.PDDocument
import org.apache.pdfbox.rendering.PDFRenderer
import org.apache.poi.ss.usermodel.{Cell, DateUtil}
import org.apache.poi.xssf.usermodel.XSSFWorkbook
import org.joda.time.DateTime
import shapeless.{HList, LabelledGeneric}
import shapeless.ops.hlist._
import shapeless.ops.record._
import shapeless.record._
import shapeless.syntax.RecordOps

import tool.Pojo._
import tool.{ConvertHelper, Tool}
//import org.apache.commons.math3.stat.StatUtils
//import org.apache.commons.math3.stat.descriptive.moment.StandardDeviation
//import org.saddle.io._
//import CsvImplicits._
//import javax.imageio.ImageIO
import org.apache.commons.codec.binary.Base64
//import org.apache.pdfbox.pdmodel.PDDocument
//import org.apache.pdfbox.rendering.PDFRenderer
import play.api.libs.json.Json

import scala.collection.JavaConverters._
import scala.collection.mutable
import scala.concurrent.duration.Duration
import scala.concurrent.{Await, Future}
import scala.math.log10
import Implicits._
import implicits.Implicits._


object Utils {

  val projectName="metabolite_platform"
  val dbName = s"${projectName}_database"
  val windowsPath = s"E:\\${dbName}"
  val playPath = new File("../").getAbsolutePath
  val linuxPath = playPath + s"/${dbName}"
  val isWindows = {
    if (new File(windowsPath).exists()) true else false
  }

  val path = {
    if (new File(windowsPath).exists()) windowsPath else linuxPath
  }
  val exampleFile = new File(path, "example")

  val binPath = new File(path, "bin")
  val dataDir = new File(path, "data")
  val pdfDir = new File(dataDir, "pdf")
  val rPath = {
    val rPath = s"C:\\workspaceForIDEA\\${projectName}\\server\\rScripts"
    val linuxRPath = linuxPath + "/rScripts"
    if (new File(rPath).exists()) rPath else linuxRPath
  }

  val tocFile = {
    val windowsFile = new File(s"C:\\workspaceForIDEA\\${projectName}\\server\\app\\sources\\toc.xsl")
    val linuxFile = new File(dataDir, "toc.xsl")
    if (windowsFile.exists()) windowsFile else linuxFile
  }

  val tocEnFile = {
    val windowsFile = new File(s"C:\\workspaceForIDEA\\${projectName}\\server\\app\\sources\\toc_en.xsl")
    val linuxFile = new File(dataDir, "toc_en.xsl")
    if (windowsFile.exists()) windowsFile else linuxFile
  }

  val userDir = new File(path, "user")

  val windowsTestDir = new File("C:\\temp")
  val linuxTestDir = new File(playPath, "workspace")
  val testDir = if (windowsTestDir.exists()) windowsTestDir else linuxTestDir
  val wkhtmltopdf = "wkhtmltopdf"
  val cp = if (isWindows) "xcopy" else "cp"

  def callLinuxScript(tmpDir: File, shBuffer: List[String], shPrefix: String = "") = {
    val execCommand = new ExecCommand
    val runFile = new File(tmpDir, s"${shPrefix}run.sh")
    FileUtils.writeLines(runFile, shBuffer.asJava)
    val dos2Unix = s"${("dos2unix").wsl} ${runFile.unixPath} "
    val shCommand = s"${("sh").wsl} ${runFile.unixPath}"
    execCommand.exec(dos2Unix, shCommand, tmpDir)
    execCommand
  }


  def getJsonByConfigFile(workspaceDir: File) = {
    val configFile = Tool.getConfigFile(workspaceDir)
    Json.parse(configFile.str)
  }

  def killPid(pidDir: File) = {
    val tmpDir = Files.createTempDirectory("tmpDir").toFile

    def loop(acc: List[String], files: List[File]): List[String] = {
      files match {
        case Nil => acc
        case x :: xs if x.isFile && x.getName == "pid.txt" => val pid = x.str.trim
          loop(pid :: acc, xs)
        case x :: xs if x.isDirectory =>
          loop(acc, xs ::: x.listFiles().toList)
        case x :: xs =>
          loop(acc, xs)
      }
    }

    val pids = loop(List(), List(pidDir))

    val commands = pids.map { pid =>
      s"""
         |kill -s 9 ${pid}
         |""".stripMargin
    }
    val execComand = Utils.callLinuxScript(tmpDir, commands)
    Utils.deleteDirectory(tmpDir)
  }

  def deletePidFile(pidDir: File) = {
    def loop(file: File): Unit = {
      for (file <- file.listFiles()) {
        if (file.isDirectory) {
          loop(file)
        } else {
          if (file.getName == "pid.txt") {
            FileUtils.deleteQuietly(file)
          }
        }
      }
    }

    loop(pidDir)
  }

  def orderCallLinuxScript(tmpDir: File, shBuffer: List[String]): ExecCommand = {
    val runFile = new File(tmpDir, s"run.sh")
    val execCommand = new ExecCommand
    val pidFile = new File(tmpDir, "pid.txt")
    val pidCommand =
      s"""
         |echo $$$$ > ${pidFile.unixPath}
         |export LD_LIBRARY_PATH=/home/dev/anaconda3/pkgs/openjdk-11.0.9.1-h5cc2fde_1/lib/server/
         |""".stripMargin
    val deletePidCommand =
      s"""
         |rm ${pidFile.unixPath}
         |""".stripMargin
    val newBuffer = List(pidCommand) ::: shBuffer ::: List(deletePidCommand)
    val shStr = newBuffer.flatMap { line =>
      line.split("\r\n")
    }.notEmptyLines.notAnnoLines.mkString(" &&\\\n")
    (shStr + "\n").toFile(runFile)
    val dos2Unix = s"${("dos2unix").wsl} ${runFile.unixPath} "
    val shCommand = s"${("sh").wsl} ${runFile.unixPath}"
    execCommand.exec(dos2Unix, shCommand, tmpDir)
    execCommand
  }

  val Rscript = {
    "Rscript"
  }

  def callScript(tmpDir: File, shBuffer: List[String]) = {
    val execCommand = new ExecCommand
    val runFile = if (Utils.isWindows) {
      new File(tmpDir, "run.bat")
    } else {
      new File(tmpDir, "run.sh")
    }
    FileUtils.writeLines(runFile, shBuffer.asJava)
    val shCommand = runFile.getAbsolutePath
    if (Utils.isWindows) {
      execCommand.exec(shCommand, tmpDir)
    } else {
      val useCommand = "chmod +x " + runFile.getAbsolutePath
      val dos2Unix = "dos2unix " + runFile.getAbsolutePath
      execCommand.exec(dos2Unix, useCommand, shCommand, tmpDir)
    }
    execCommand
  }


  def deleteDirectory(direcotry: File) = {
    try {
      FileUtils.deleteDirectory(direcotry)
    } catch {
      case _ =>
    }
  }

  def getTime(startTime: Long) = {
    val endTime = System.currentTimeMillis()
    (endTime - startTime) / 1000.0
  }

  def spentTime(f: () => Unit) = {
    val startTime = System.currentTimeMillis()
    f()
    val time = getTime(startTime)
    println(time)
  }

  def isDoubleP(value: String, p: Double => Boolean): Boolean = {
    try {
      val dbValue = value.toDouble
      p(dbValue)
    } catch {
      case _: Exception =>
        false
    }
  }

  def StandardDeviation(x: Seq[Double]): Double = {
    val sd = new StandardDeviation()
    sd.evaluate(x.toArray)
  }

  def lfJoin(seq: Seq[String]) = {
    seq.mkString("\n")
  }

  def execFuture[T](f: Future[T]): T = {
    Await.result(f, Duration.Inf)
  }

  def getValue[T](kind: T, noneMessage: String = "暂无"): String = {
    kind match {
      case x if x.isInstanceOf[DateTime] => val time = x.asInstanceOf[DateTime]
        time.toString("yyyy-MM-dd HH:mm:ss")
      case x if x.isInstanceOf[Option[T]] => val option = x.asInstanceOf[Option[T]]
        if (option.isDefined) getValue(option.get, noneMessage) else noneMessage
      case x if x.isInstanceOf[Seq[T]] => val list = x.asInstanceOf[Seq[T]]
        list.mkString(";")
      case _ => kind.toString
    }
  }

  def getValues[T](kind: T, noneMessage: String = "暂无", field: Field): List[String] = {
    kind match {
      case x if x.isInstanceOf[DateTime] => val time = x.asInstanceOf[DateTime]
        val line = List(field.getName, time.toString("yyyy-MM-dd HH:mm:ss")).mkString(",")
        List(line)
      case x if x.isInstanceOf[Option[T]] => val option = x.asInstanceOf[Option[T]]
        if (option.isDefined) getValues(option.get, noneMessage, field) else {
          List[String]()
        }
      case x if x.isInstanceOf[Seq[T]] => val list = x.asInstanceOf[Seq[T]]
        val line = List(field.getName, list.mkString(";")).mkString(",")
        List(line)
      case x if x.isInstanceOf[MyConfig] => val myConfig = x.asInstanceOf[MyConfig]
        myConfig.getClass.getDeclaredFields.toList.map { x: Field =>
          x.setAccessible(true)
          val kind = x.get(myConfig)
          val value = getValue(kind, "")
          List(s"${field.getName}.${x.getName}", value).mkString(",")
        }.filter(_.split(",").size >= 2)
      case _ =>
        val line = List(field.getName, kind.toString).mkString(",")
        List(line)
    }
  }


  def getArrayByTs[T](x: Seq[T]) = {
    x.map { y =>
      y.getClass.getDeclaredFields.toBuffer.map { x: Field =>
        x.setAccessible(true)
        val kind = x.get(y)
        val value = getValue(kind)
        (x.getName, value)
      }.init.toMap
    }
  }

  def getJsonByT[T, R <: HList](y: T)(implicit gen: LabelledGeneric.Aux[T, R],
                                      toMap: ToMap.Aux[R, Symbol, Any]) = {
    val map = gen.to(y).toMap.map { case (symbol, value) =>
      (symbol.name, getValue(value))
    }
    Json.toJson(map)
  }

  def t2ConfigFile[T, R <: HList](workspaceDir: File, data: T)(implicit gen: LabelledGeneric.Aux[T, R],
                                                               toMap: ToMap.Aux[R, Symbol, Any]) = {
    val configFile = Tool.getConfigFile(workspaceDir)
    val json = getJsonByT(data)
    Json.stringify(json).toFile(configFile)
  }

  def getLinesByT[T](y: T)(implicit sep: String = ",") = {
    y.getClass.getDeclaredFields.toList.flatMap { x: Field =>
      x.setAccessible(true)
      val kind = x.get(y)
      getValues(kind, "", x)
    }.notEmptyLines
  }

  def getJsonByTs[T](x: Seq[T]) = {
    val array = getArrayByTs(x)
    Json.toJson(array)
  }

  def peakAreaNormal(dataFile: File, coefficient: Double) = {
    val buffer = FileUtils.readLines(dataFile).asScala
    val array = buffer.map(_.split("\t"))
    val sumArray = new Array[Double](array(0).length)
    for (i <- 1 until array.length; j <- 1 until array(i).length) {
      sumArray(j) += array(i)(j).toDouble
    }
    for (i <- 1 until array.length; j <- 1 until array(i).length) {
      array(i)(j) = (coefficient * array(i)(j).toDouble / sumArray(j)).toString
    }
    FileUtils.writeLines(dataFile, array.map(_.mkString("\t")).asJava)
  }


  def log2(x: Double) = log10(x) / log10(2.0)

  //  def getStdErr(values: Array[Double]) = {
  //    val standardDeviation = new StandardDeviation
  //    val stderr = standardDeviation.evaluate(values) / Math.sqrt(values.length)
  //    stderr
  //  }

  def dealGeneIds(geneId: String) = {
    geneId.split("\n").map(_.trim).distinct.toBuffer
  }

  def getDataJson(file: File) = {
    val lines = FileUtils.readLines(file).asScala
    val sampleNames = lines.head.split("\t").drop(1)
    val array = lines.drop(1).map { line =>
      val columns = line.split("\t")
      val map = mutable.Map[String, String]()
      map += ("geneId" -> columns(0))
      sampleNames.zip(columns.drop(1)).foreach { case (sampleName, data) =>
        map += (sampleName -> data)
      }
      map
    }
    Json.obj("array" -> array, "sampleNames" -> sampleNames)
  }

  def dealInputFile(file: File) = {
    val lines = FileUtils.readLines(file).asScala
    val buffer = lines.map(_.trim)
    FileUtils.writeLines(file, buffer.asJava)
  }

  def dealFileHeader(file: File) = {
    val lines = FileUtils.readLines(file).asScala
    val headers = lines(0).split("\t")
    headers(0) = ""
    lines(0) = headers.mkString("\t")
    FileUtils.writeLines(file, lines.asJava)
  }


  def pdf2png(tmpDir: File, fileName: String) = {
    val pdfFile = new File(tmpDir, fileName)
    val outFileName = fileName.substring(0, fileName.lastIndexOf(".")) + ".png"
    val outFile = new File(tmpDir, outFileName)
    val document = PDDocument.load(pdfFile)
    val renderer = new PDFRenderer(document)
    ImageIO.write(renderer.renderImage(0, 3), "png", outFile)
    document.close()
  }

  def pdf2png(pdfFile: File, pngFile: File) = {
    val tmpDir = Files.createTempDirectory("tmpDir").toFile
    val command =
      s"""
         |convert  -density 300 ${pdfFile.getAbsoluteFile.unixPath} ${pngFile.getAbsoluteFile.unixPath}
         |""".stripMargin
    Utils.callLinuxScript(tmpDir, List(command))
    Utils.deleteDirectory(tmpDir)
  }

  def pdf2pngs(ts: List[(File, File)])(implicit tool: Tool, modeDao: ModeDao) = {
    val tmpDir = Tool.createTempDirectory("tmpDir")
    val commmands = ts.map { case (pdfFile, pngFile) =>
      s"""
         |convert  -density 300 ${pdfFile.getAbsoluteFile.unixPath} ${pngFile.getAbsoluteFile.unixPath}
         |""".stripMargin
    }
    Utils.callLinuxScript(tmpDir, commmands)
    Tool.deleteDirectory(tmpDir)
  }


  def svg2png(svgFile: File, pngFile: File) = {
    val input = new TranscoderInput(svgFile.toURI.toString)
    val outStream = new FileOutputStream(pngFile)
    val output = new TranscoderOutput(outStream)
    val t = new PNGTranscoder()
    t.transcode(input, output)
    outStream.flush()
    outStream.close()
  }

  def getInfoByFile(file: File) = {
    val lines = FileUtils.readLines(file).asScala
    val columnNames = lines.head.split("\t").drop(1)
    val array = lines.drop(1).map { line =>
      val columns = line.split("\t")
      val map = mutable.Map[String, String]()
      map += ("geneId" -> columns(0))
      columnNames.zip(columns.drop(1)).foreach { case (columnName, data) =>
        map += (columnName -> data)
      }
      map
    }
    (columnNames, array)
  }

  def checkFile(file: File): (Boolean, String) = {
    val buffer = FileUtils.readLines(file).asScala
    val headers = buffer.head.split("\t")
    var error = ""
    if (headers.size < 2) {
      error = "错误：文件列数小于2！"
      return (false, error)
    }
    val headersNoHead = headers.drop(1)
    val repeatElement = headersNoHead.diff(headersNoHead.distinct).distinct.headOption
    repeatElement match {
      case Some(x) => val nums = headers.zipWithIndex.filter(_._1 == x).map(_._2 + 1).mkString("(", "、", ")")
        error = "错误：样品名" + x + "在第" + nums + "列重复出现！"
        return (false, error)
      case None =>
    }

    val ids = buffer.drop(1).map(_.split("\t")(0))
    val repeatid = ids.diff(ids.distinct).distinct.headOption
    repeatid match {
      case Some(x) => val nums = ids.zipWithIndex.filter(_._1 == x).map(_._2 + 2).mkString("(", "、", ")")
        error = "错误：第一列:" + x + "在第" + nums + "行重复出现！"
        return (false, error)
      case None =>
    }

    val headerSize = headers.size
    for (i <- 1 until buffer.size) {
      val columns = buffer(i).split("\t")
      if (columns.size != headerSize) {
        error = "错误：数据文件第" + (i + 1) + "行列数不对！"
        return (false, error)
      }

    }

    for (i <- 1 until buffer.size) {
      val columns = buffer(i).split("\t")
      for (j <- 1 until columns.size) {
        val value = columns(j)
        if (!isDouble(value)) {
          error = "错误：数据文件第" + (i + 1) + "行第" + (j + 1) + "列不为数字！"
          return (false, error)
        }
      }
    }
    (true, error)
  }

  def isDouble(value: String): Boolean = {
    try {
      value.toDouble
    } catch {
      case _: Exception =>
        return false
    }
    true
  }

  def getBase64Str(imageFile: File): String = {
    val inputStream = new FileInputStream(imageFile)
    val bytes = IOUtils.toByteArray(inputStream)
    val bytes64 = Base64.encodeBase64(bytes)
    inputStream.close()
    new String(bytes64)
  }

  def to[A]: ConvertHelper[A] = {
    import tool.FromMap._
    new ConvertHelper[A]
  }

  def getFieldNamesByTs[T, R <: HList, K <: HList, V <: HList](ys: List[T])(
    implicit gen: LabelledGeneric.Aux[T, R], keys: Keys.Aux[R, K], ktl: ToList[K, Symbol]) = {
    keys().toList.map(_.name)
  }


}
